1 /** 2 * Author: KonstantIMP <mihedovkos@gmail.com> 3 * Date: Jun 26 2021 4 */ 5 module linker.loader; 6 7 import linker.exception; 8 import std.string : toStringz, fromStringz; 9 10 /** Extern function for setting path for dll loading */ 11 extern(Windows) { 12 int SetDllDirectoryA(const(char)* path); 13 } 14 15 /** 16 * Class for runtime libraries loading 17 */ 18 public class Linker { 19 /** Contain lib handler by name */ 20 private static void * [string] loadedLibs; 21 /** Contain failed symbols loads by lib */ 22 private static string[][string] failedLoads; 23 24 /** 25 * Function for catching unsopported symbols 26 */ 27 extern(C) static void unsupportedSymbol() { 28 throw new LinkException("The function you are calling is not pressent in your version of lib"); 29 } 30 31 /** 32 * Link the provided symbol 33 * Params: 34 * func = The function we are linking 35 * symbol = Symbol for linking 36 * libs = Libraries for symbol searching 37 */ 38 public static void link(T)(ref T func, string symbol, const string [] libs ...) { 39 func = cast(T)getSymbol(symbol, libs); 40 } 41 42 /** 43 * Get the symbol from the lib 44 * Params: 45 * symbol = Symbol for import 46 * libs = Libraries for symbol searching 47 * Returns: Handle for the symbol 48 */ 49 public static void * getSymbol(string symbol, const string [] libs ...) { 50 void * handle = null; 51 52 foreach (lib; libs) { 53 if (lib !in loadedLibs) loadLibrary(lib); 54 55 handle = getSymbolOS(loadedLibs[lib], symbol); 56 57 if (handle !is null) break; 58 } 59 60 if (handle is null) { 61 foreach (lib; libs) { 62 failedLoads[lib] ~= symbol; 63 } 64 handle = &unsupportedSymbol; 65 } 66 67 return handle; 68 } 69 70 /** 71 * Load a dynamic lib 72 * Params: 73 * library = Library for loading 74 */ 75 public static void loadLibrary(string library) { 76 import std.algorithm.searching : canFind; 77 import std.string : split; 78 79 void * handle = null; 80 81 if (library.canFind(';')) { 82 foreach (lib; library.split(';')) { 83 handle = loadLibraryOS(lib); 84 if (handle !is null) break; 85 } 86 } 87 else handle = loadLibraryOS(library); 88 89 if (handle is null) throw new LinkException("Library load failed ("~ library ~"): "~ getLastErrorMessageOS()); 90 91 loadedLibs[library] = handle; 92 } 93 94 /** 95 * Unload a library 96 * Params: 97 * library = Library for unloading 98 */ 99 public static void unloadLibrary(string library) { 100 unloadLibraryOS(loadedLibs[library]); 101 loadedLibs.remove(library); 102 } 103 104 /** 105 * Check load state 106 * Returns: True if was load fails 107 */ 108 public static bool isFails() { 109 return failedLoads.length != 0; 110 } 111 112 /** 113 * Getter for loaded libs 114 * Returns: Loaded libs list 115 */ 116 public static string [] getLoaded() { 117 return loadedLibs.keys; 118 } 119 120 /** 121 * Check for lib loading 122 * Params: 123 * lib = Lib for checking 124 * Returns: true if the lib was loaded 125 */ 126 public static bool isLoaded(string lib) { 127 if(lib in loadedLibs) return true; 128 return false; 129 } 130 131 /** 132 * Check failed library loads 133 * Params: 134 * lib = Lib for checking 135 * Returns: Failed loads for the library 136 */ 137 public static string [] getFails(string lib) { 138 if (lib in failedLoads) return failedLoads[lib]; 139 return null; 140 } 141 142 /** 143 * Unload all loaded libs at exit 144 */ 145 static ~this() { 146 foreach(lib; loadedLibs.keys) { 147 unloadLibrary(lib); 148 } 149 } 150 151 /** Functions for platform specific library load */ 152 version (Windows) { 153 /** Specific imports */ 154 import core.sys.windows.winnt : IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386; 155 import core.sys.windows.winbase : LoadLibraryA, GetProcAddress, FreeLibrary; 156 157 /** 158 * Load library by using OS-specific functions 159 * Params: 160 * library = Library's for loading name 161 * Returns: Loaded library if everything ok or null in other cases 162 */ 163 private static void * loadLibraryOS(string library) { 164 setDLLPath(); 165 return LoadLibraryA(cast(char *)toStringz(library)); 166 } 167 168 /** 169 * Get symbol from library by using OS-specific functions 170 * Params: 171 * handle = A handle to the DLL module that contains the function or variable 172 * symbol = The function or variable name, or the function's ordinal value 173 * Returns: Address of symbol or null 174 */ 175 private static void * getSymbolOS(void * handle, string symbol) { 176 return GetProcAddress(handle, cast(char *)toStringz(symbol)); 177 } 178 179 /** 180 * Unload library by using OS-specific functions 181 * Params: 182 * lib = Library for unloading 183 * Returns: True if everything is ok 184 */ 185 private static bool unloadLibraryOS(void * lib) { 186 return cast(bool)FreeLibrary(lib); 187 } 188 189 /** 190 * Get the last error message by using OS-specific functions 191 * Returns: String with error message or nothing 192 */ 193 private static string getLastErrorMessageOS() { 194 import core.sys.windows.winbase : GetLastError, FormatMessageA, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_ARGUMENT_ARRAY; 195 import core.sys.windows.winnt : LANG_NEUTRAL; 196 import core.stdc.string : memset; 197 198 char [] buffer = new char[2048]; 199 memset(buffer.ptr, '\0', 2048); 200 201 FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, 202 null, GetLastError(), LANG_NEUTRAL, buffer.ptr, 2048, cast(char **)["\0".ptr].ptr); 203 204 return fromStringz(buffer.ptr).idup; 205 } 206 207 /** 208 * Say Windows where it should search dll 209 */ 210 private static void setDLLPath() { 211 static bool is_set = false; 212 213 if (is_set) return; 214 215 string gtk_path = findGtkLibs(); 216 217 if (gtk_path !is null) { 218 SetDllDirectoryA((gtk_path ~ '\0').ptr); 219 } 220 221 is_set = true; 222 } 223 224 /** 225 * Find Gtk libs on the computer 226 * Returns: Path to the Gtk libs or null 227 */ 228 private static string findGtkLibs() { 229 import std.algorithm.iteration : splitter; 230 import std.process : environment; 231 import std.path : buildNormalizedPath; 232 import std.file : exists; 233 234 foreach (lib; [`libgtk-3-0.dll`, `libgtk-4-1.dll`, `gtk-3.dll`, `gtk-4.dll`]) { 235 foreach (path; splitter(environment.get("PATH"), ';')) { 236 string dll_path = buildNormalizedPath(path, lib); 237 238 if (exists(dll_path) == false) continue; 239 if (checkDLLArch(dll_path)) return path; 240 } 241 } 242 243 return null; 244 } 245 246 /** 247 * Check a DLL for compatible with the machine 248 * Params: 249 * dll_path = Path to the dll for check 250 * Returns: true if the dll is compatible with the machine 251 */ 252 private static bool checkDLLArch(string dll_path) { 253 import std.stdio : File; 254 255 File dll = File(dll_path); 256 257 dll.seek(0xc3); 258 dll.seek(cast(int)dll.rawRead(new int[1])[0]); 259 260 if (cast(uint)dll.rawRead(new uint[1])[0] != 0x00004550) return false; 261 262 ushort win_type = cast(ushort)dll.rawRead(new ushort[1])[0]; 263 264 version(Win32) { 265 return win_type == IMAGE_FILE_MACHINE_I386; 266 } 267 version(Win64) { 268 return win_type == IMAGE_FILE_MACHINE_AMD64; 269 } 270 } 271 } 272 else { 273 /** Import OS-specific libs */ 274 import core.sys.posix.dlfcn : dlopen, dlerror, dlsym, dlclose; 275 import core.sys.posix.dlfcn : RTLD_NOW, RTLD_GLOBAL; 276 277 import std.path : buildPath; 278 279 /** String for containing last error message */ 280 private static string last_error = null; 281 282 version(OSX) { 283 /** 284 * Find the path to the libs on MacOS 285 * Returns: Path to the libs in the MacOS 286 */ 287 private static string getBasePath() { 288 import std.process : environment; 289 import std.path : buildPath; 290 291 static string path = null; 292 293 if (path !is null) return path; 294 295 path = environment.get("GTK_BASEPATH"); 296 if (path is null) { 297 path = environment.get("HOMEBREW_ROOT"); 298 if (path !is null) path = buildPath(path, "lib"); 299 } 300 301 return path; 302 } 303 304 private static string base_path = getBasePath(); 305 } 306 else { 307 private static string base_path = ""; 308 } 309 310 /** 311 * Load library by using OS-specific functions 312 * Params: 313 * library = Library's for loading name 314 * Returns: Loaded library if everything ok or null in other cases 315 */ 316 private static void * loadLibraryOS(string library) { 317 import std.path : buildPath; 318 319 void * handle = dlopen(cast(char *)toStringz(buildPath(base_path, library)), RTLD_GLOBAL | RTLD_NOW); 320 321 if (!handle) last_error = fromStringz(dlerror()).idup; 322 323 return handle; 324 } 325 326 /** 327 * Get symbol from library by using OS-specific functions 328 * Params: 329 * handle = A handle to the DLL module that contains the function or variable 330 * symbol = The function or variable name, or the function's ordinal value 331 * Returns: Address of symbol or null 332 */ 333 private static void * getSymbolOS(void * handle, string symbol) { 334 void * symbol_handle = dlsym(handle, cast(char *)toStringz(symbol)); 335 336 if (!symbol_handle) last_error = fromStringz(dlerror()).idup; 337 338 return symbol_handle; 339 } 340 341 /** 342 * Unload library by using OS-specific functions 343 * Params: 344 * lib = Library for unloading 345 * Returns: True if everything is ok 346 */ 347 private static bool unloadLibraryOS(void * lib) { 348 int res = dlclose(lib); 349 if (res != 0) last_error = fromStringz(dlerror()).idup; 350 return res == 0; 351 } 352 353 /** 354 * Get the last error message by using OS-specific functions 355 * Returns: String with error message or nothing 356 */ 357 private static string getLastErrorMessageOS() { 358 scope(exit) last_error = null; 359 return last_error; 360 } 361 } 362 }